package com.clp.util.hutool.core.bean;

import com.clp.util.hutool.core.annotation.AnnotationUtil;
import com.clp.util.hutool.core.annotation.PropIgnore;
import com.clp.util.hutool.core.convert.Convert;
import com.clp.util.hutool.core.util.ClassUtil;
import com.clp.util.hutool.core.util.ModifierUtil;
import com.clp.util.hutool.core.util.ReflectUtil;
import com.clp.util.hutool.core.util.TypeUtil;

import java.beans.Transient;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

/**
 * 属性描述，包括了字段、getter、setter和相应的方法执行
 *
 * @author looly
 */
public class PropDesc {
    final Field field;
    protected Method getter;
    protected Method setter;

    public PropDesc(Field field, Method getter, Method setter) {
        this.field = field;
        this.getter = ClassUtil.setAccessible(getter);
        this.setter = ClassUtil.setAccessible(setter);
    }

    public String getFieldName() {
        return ReflectUtil.getFieldName(this.field);
    }

    /**
     * 检查属性是否可读（即是否可以通过{@link #getValue(Object)}获取到值）
     * @param checkTransient 是否检查Transient关键字或注解
     * @return 是否可读
     * @since 5.4.2
     */
    public boolean isReadable(boolean checkTransient) {
        // 检查是否有getter方法或是否为public修饰
        if (null == this.getter && !ModifierUtil.isPublic(this.field)) {
            return false;
        }

        // 检查transient关键字和@Transient注解
        if (checkTransient && isTransientForGet()) {
            return false;
        }

        // 检查@PropIgnore注解
        return !isIgnoreSet();
    }

    /**
     * 字段和Getter方法是否为Transient关键字修饰的
     *
     * @return 是否为Transient关键字修饰的
     * @since 5.3.11
     */
    private boolean isTransientForGet() {
        boolean isTransient = ModifierUtil.hasModifier(this.field, ModifierUtil.ModifierType.TRANSIENT);

        // 检查Getter方法
        if (!isTransient && null != this.getter) {
            isTransient = ModifierUtil.hasModifier(this.getter, ModifierUtil.ModifierType.TRANSIENT);

            // 检查注解
            if (!isTransient) {
                isTransient = AnnotationUtil.hasAnnotation(this.getter, Transient.class);
            }
        }

        return isTransient;
    }

    /**
     * 检查属性是否可写（即是否可以通过{@link # getValue(Object)}获取到值）
     * @param checkTransient 是否检查Transient关键字或注解
     * @return 是否可读
     * @since 5.4.2
     */
    public boolean isWritable(boolean checkTransient) {
        // 检查是否有getter方法或是否为public修饰
        if(null == this.setter && !ModifierUtil.isPublic(this.field)) {
            return false;
        }

        // 检查transient关键字和@Transient注解
        if (checkTransient && isTransientForSet()) {
            return false;
        }

        // 检查 @PropIgnore 注解
        return !isIgnoreSet();
    }

    /**
     * 字段和Getter方法是否为Transient关键字修饰的
     *
     * @return 是否为Transient关键字修饰的
     * @since 5.3.11
     */
    private boolean isTransientForSet() {
        boolean isTransient = ModifierUtil.hasModifier(this.field, ModifierUtil.ModifierType.TRANSIENT);

        // 检查Getter方法
        if (!isTransient && null != this.setter) {
            isTransient = ModifierUtil.hasModifier(this.setter, ModifierUtil.ModifierType.TRANSIENT);

            // 检查注解
            if (!isTransient) {
                isTransient = AnnotationUtil.hasAnnotation(this.setter, Transient.class);
            }
        }

        return isTransient;
    }

    /**
     * 检查字段是否被忽略写，通过{@link PropIgnore} 注解完成，规则为：
     * <pre>
     *     1. 在字段上有{@link PropIgnore} 注解
     *     2. 在setXXX方法上有{@link PropIgnore} 注解
     * </pre>
     *
     * @return 是否忽略写
     * @since 5.4.2
     */
    private boolean isIgnoreSet() {
        return AnnotationUtil.hasAnnotation(this.field, PropIgnore.class)
                || AnnotationUtil.hasAnnotation(this.setter, PropIgnore.class);
    }

    /**
     * 获得字段类型<br>
     * 先获取字段的类型，如果字段不存在，则获取Getter方法的返回类型，否则获取Setter的第一个参数类型
     *
     * @return 字段类型
     */
    public Type getFieldType() {
        if (null != this.field) {
            return TypeUtil.getType(this.field);
        }
        return findPropType(getter, setter);
    }

    /**
     * 通过Getter和Setter方法中找到属性类型
     *
     * @param getter Getter方法
     * @param setter Setter方法
     * @return {@link Type}
     */
    private Type findPropType(Method getter, Method setter) {
        Type type = null;
        if (null != getter) {
            type = TypeUtil.getReturnType(getter);
        }
        if (null == type && null != setter) {
            type = TypeUtil.getParamType(setter, 0);
        }
        return type;
    }

    public PropDesc setValue(Object bean, Object value, boolean ignoreNull, boolean ignoreError) {
        if (ignoreNull && null == value) {
            return this;
        }

        // 当类型不匹配的时候，执行默认转换
        if (null != value) {
            final Class<?> propClass = getFieldClass();
            if (!propClass.isInstance(value)) {
                value = Convert.convertWithCheck(propClass, value, null, ignoreError);
            }
        }

        // 属性赋值
        if (null != value || !ignoreNull) {
            try {
                this.setValue(bean, value);
            } catch (Exception e) {
                if (!ignoreError) {
                    throw new BeanException(e, "Set value of [{}] error!", getFieldName());
                }
                // 忽略注入失败
            }
        }

        return this;
    }

    /**
     * 设置Bean的字段值<br>
     * 首先调用字段对应的Setter方法，如果Setter方法不存在，则判断字段如果为public，则直接赋值字段值<br>
     * 此方法不检查任何注解，使用前需调用 {@link #isWritable(boolean)} 检查是否可写
     *
     * @param bean  Bean对象
     * @param value 值，必须与字段值类型匹配
     * @return this
     * @since 4.0.5
     */
    public PropDesc setValue(Object bean, Object value) {
        if (null != this.setter) {
            ReflectUtil.invoke(bean, this.setter, value);
        } else if (ModifierUtil.isPublic(this.field)) {
            ReflectUtil.setFieldValue(bean, this.field, value);
        }
        return this;
    }

    /**
     * 获得字段类型<br>
     * 先获取字段的类型，如果字段不存在，则获取Getter方法的返回类型，否则获取Setter的第一个参数类型
     *
     * @return 字段类型
     */
    public Class<?> getFieldClass() {
        if (null != this.field) {
            return TypeUtil.getClass(this.field);
        }
        return findPropClass(getter, setter);
    }

    /**
     * 通过Getter和Setter方法中找到属性类型
     *
     * @param getter Getter方法
     * @param setter Setter方法
     * @return {@link Type}
     */
    private Class<?> findPropClass(Method getter, Method setter) {
        Class<?> type = null;
        if (null != getter) {
            type = TypeUtil.getReturnClass(getter);
        }
        if (null == type && null != setter) {
            type = TypeUtil.getFirstParamClass(setter);
        }
        return type;
    }

    /**
     * 获取属性值<br>
     * 首先调用字段对应的Getter方法获取值，如果Getter方法不存在，则判断字段如果为public，则直接获取字段值<br>
     * 此方法不检查任何注解，使用前需调用 {@link # isReadable(boolean)} 检查是否可读
     *
     * @param bean Bean对象
     * @return 字段值
     * @since 4.0.5
     */
    public Object getValue(Object bean) {
        if (null != this.getter) {
            return ReflectUtil.invoke(bean, this.getter);
        } else if (ModifierUtil.isPublic(this.field)) {
            return ReflectUtil.getFieldValue(bean, this.field);
        }
        return null;
    }

    /**
     * 获取属性值，自动转换属性值类型<br>
     * 首先调用字段对应的Getter方法获取值，如果Getter方法不存在，则判断字段如果为public，则直接获取字段值
     *
     * @param bean        Bean对象
     * @param targetType   返回属性值需要转换的类型，null表示不转换
     * @param ignoreError 是否忽略错误，包括转换错误和注入错误
     * @return this
     * @since 5.4.2
     */
    public Object getValue(Object bean, Type targetType, boolean ignoreError) {
        Object result = null;
        try {
            result = getValue(bean);
        } catch (Exception e) {
            if (!ignoreError) {
                throw new BeanException(e, "Get value of [{}] error!", getFieldName());
            }
        }

        if (null != result && null != targetType) {
            // 尝试将结果转换为目标类型，如果转换失败，返回原类型
            final Object convertValue = Convert.convertWithCheck(targetType, result, null, ignoreError);
            if (null != convertValue) {
                result = convertValue;
            }
        }

        return result;
    }
}
